//
//  MGPagingView.swift
//  FuncTest
//
//  Created by li on 2025-05-16.
//

import SwiftUI
import SwiftUIIntrospect

public struct MGPagingView<V: View, T: View>: View {
	
	@EnvironmentObject var configure: MGPagingViewConfigure
	@Environment(\.mgTabMarkView) var markView
	@ViewBuilder
	var tab: (_ index: Int) -> T
	
	@ViewBuilder
	var content: (_ index: Int, _ isLoaded: Bool) -> V
	
	public init(@ViewBuilder tab: @escaping (_ index: Int) -> T, @ViewBuilder content: @escaping (_ index: Int, _ isLoaded: Bool) -> V) {
		
		self.tab = tab
		self.content = content
	}

	public var body: some View {
		
		VStack(spacing: 0) {
			tabView()
				.mgViewVisibility(.visible.empty(!configure.showTabView))
			
			contentView()
		}
		.mgOnchange(of: configure.list.count, initial: true) { oldValue, newValue in
			
			if newValue < oldValue {
				
				for index in newValue..<oldValue {
					
					configure.tabFrames.removeValue(forKey: index)
				}
			}
			
			if newValue != oldValue {

				configure.scrollTo(configure.currentIndex, animated: false)
			}
		}
	}
}

public extension MGPagingView where T == EmptyView {
	
	public init(@ViewBuilder content: @escaping (_ index: Int, _ isLoaded: Bool) -> V) {
		
		self.content = content
		self.tab = { _ in EmptyView()}
	}
}


extension MGPagingView {
	@ViewBuilder
	func tabView() -> some View {
		GeometryReader { proxy in
			ScrollView(.horizontal, showsIndicators: false) {
				
				HStack(spacing: configure.tabItemSpacing) {
					ForEach(0..<configure.list.count, id: \.self) { index in
						
						tab(index)
							.onTapGesture {
								configure.scrollTo(index, animated: true)
							}
					}
				}
				.frame(minWidth: proxy.size.width, alignment: .init(horizontal: configure.tabAlignment, vertical: .center))
				.overlay(markContentView())
				.coordinateSpace(name: configure.tabScrollViewCoordinateSpace)
			}
			.introspectScrollView { scrollView in
				configure.configureTab(scrollView)
			}
		}
		.frame(height: 44)
	}
	
	
	@ViewBuilder
	func markContentView() -> some View {
		let frame = configure.tabFrames[configure.currentIndex] ?? .zero
		GeometryReader { proxy in
			
			HStack {
				markView
					.frame(width: frame.width, height: 2)
					.offset(x: frame.midX - frame.width / 2.0, y: proxy.size.height - 2)
			}
			.frame(width: proxy.size.width, alignment: .leading)
			.animation(.linear(duration: 0.25), value: frame)
		}
		.mgViewVisibility(.visible.empty(configure.list.count == 0))
	}
}

extension MGPagingView {

	
	@ViewBuilder
	func contentView() -> some View {
		GeometryReader { proxy in
			
			ScrollView(.horizontal, showsIndicators: configure.showsContentIndicators) {
				
				HStack(spacing: 0) {
					ForEach(0..<configure.list.count, id: \.self) { index in
						let index = Int(index)
						LazyPageView(currentIndex: $configure.currentIndex, index: index, dragLength: configure.offsetX, content: content)
							.frame(width: proxy.size.width, height: proxy.size.height)
//							.offset(x: offsetSpacing(index))
							
					}
				}
//				.offset(x: offsetX(proxy))
			}
			.coordinateSpace(name: configure.coordinateSpace)
			.mgOnchange(of: configure.currentIndex, initial: true) { index in
				
				
				guard configure.list.count > 0 else{
					return
				}
		
				if configure.currentIndex < 0 {
					configure.currentIndex = 0
				}
				
				if configure.currentIndex > configure.list.count - 1 {
					configure.currentIndex = configure.list.count - 1
				}
				
				configure.tabScrollViewScrollToCenter(true)
			}
			.introspectScrollView({ scrollView in
				configure.proxy = proxy
				configure.configure(scrollView)
			})
		}
	
	}
	
//	func offsetX(_  proxy: GeometryProxy) -> CGFloat {
//		let count = configure.count - 1
//		guard count > 0 else {
//			return 0
//		}
//		let page = proxy.size.width
//		let totalLength = CGFloat(count - 1) * page
//		
//		let progress = configure.offsetX / totalLength
//		
//		let dif = CGFloat(count - 1) * configure.contentSpacing
//		let offset = progress * dif
//
//		return offset
//	}
	
//	func offsetSpacing(_ index: Int) -> CGFloat {
//		return CGFloat(index) * configure.contentSpacing
//	}
	
	struct LazyPageView: View {
		@Binding var currentIndex: Int
		let index: Int
		let dragLength: CGFloat
		
		@ViewBuilder
		var content: (_ index: Int, _ isLoaded: Bool) -> V
		
		@State
		private var isLoaded: Bool = false
		
		var body: some View {
			GeometryReader { proxy in
				ZStack {
					if isLoaded {
						content(index, true)
					}else {
						content(index, false)
					}
				}
				.opacity(opacity(proxy))
			}
			.mgOnchange(of: currentIndex, initial: true) { newValue in
				
				if newValue == index {
					
					isLoaded = true
				}
			}
		}
		
		
		func opacity(_ proxy: GeometryProxy) -> CGFloat {
			let page = proxy.size.width
			let result: CGFloat = abs(dragLength) >= CGFloat(index - 1) * page && abs(dragLength) <= CGFloat(index + 1) * page ? 1.0 : 0
			
			return result
		}
	}
}



public extension View {
	
	@ViewBuilder
	public func mgScrollPagingAble(_ isEnable: Bool = true) -> some View  {
		if #available(iOS 17.0, *) {
			if isEnable {
				self.scrollTargetBehavior(.paging)
			}else {
				self
			}
		}else {
			introspectScrollView { scrollView in
				scrollView.isPagingEnabled = isEnable
			}
		}
	}
	
	@ViewBuilder
	public func mgPagingView(_ configure: MGPagingViewConfigure) -> some View {
		environmentObject(configure)
	}
	
	@ViewBuilder
	public func mgFrame(_ coordinate: AnyHashable, _ action: @escaping (_ rect: CGRect) -> Void) -> some View {
		background(
			GeometryReader(content: { proxy in
				Color.clear
					.preference(key: MGFramePreferenceKey.self, value: proxy.frame(in: .named(coordinate)))
				
			})
			.onPreferenceChange(MGFramePreferenceKey.self) { rect in
				
				action(rect)
			}
		)
	
	}
	
	@ViewBuilder
	public func introspectScrollView(_ callback: @escaping (_ scrollView: UIScrollView) -> Void) -> some View {
		introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { scroll in
			
			callback(scroll)
		}
	}
	
	/// 标记tab 下标横线的的位置以及宽度
	@ViewBuilder
	public func mgTabItemFrame(_ index: Int) -> some View {
		modifier(MGTabItemFrameViewModifier(index: index))
	}
	
	@ViewBuilder
	public func mgTabMarkView<T: View>(_ mark: () -> T) -> some View {
		
		environment(\.mgTabMarkView, AnyView(mark()))
	}
}

struct MGTabItemFrameViewModifier: ViewModifier {
	
	let index: Int
	@EnvironmentObject var  configure: MGPagingViewConfigure
	
	func body(content: Content) -> some View {
		content
			.mgFrame(configure.tabScrollViewCoordinateSpace) { rect in
				
				configure.tabFrames[index] = rect
			}
	}
}

struct MGTabMarkViewKey: @preconcurrency EnvironmentKey {
	
	
	typealias Value = AnyView
	
	@MainActor static let defaultValue: AnyView = AnyView(Color.red)

}


extension EnvironmentValues {

	var mgTabMarkView: AnyView {
		get {
			self[MGTabMarkViewKey.self]
		}
		
		set {
			self[MGTabMarkViewKey.self] = newValue
		}
	}
}


struct MGFramePreferenceKey: PreferenceKey {
	
	static let defaultValue: CGRect = .zero
	
	static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
		value = nextValue()
	}
}

@available(iOS 14.0, *)
struct MGPagingTestView: View {
	@StateObject var configure = MGPagingViewConfigure()
	var body: some View {
		VStack {

			MGPagingView { index in
				ZStack {
					Text("\(index + 1)")
						.multilineTextAlignment(.center)
						.frame(maxHeight: .infinity)
						.padding(.horizontal, index == 2 || index == 4 ? 40 : 0)
//						.background(Color.yellow)
						.mgTabItemFrame(index)
						.padding(.horizontal, 20)
						.foregroundColor(configure.currentIndex == index ?  Color.red : Color.black)
				}
			} content: { index, isLoaded in
				
				if isLoaded {
					ZStack {
						if index == 0 {
							
							LinearGradient(colors: [Color.red, Color.white], startPoint: .topLeading, endPoint: .bottomTrailing)
						}else if index == 1 {
							LinearGradient(colors: [Color.yellow, Color.white], startPoint: .topLeading, endPoint: .bottomTrailing)
							
						}else if index == 2 {
							
							LinearGradient(colors: [Color.blue, Color.white], startPoint: .topLeading, endPoint: .bottomTrailing)
						}else if index == 3 {
							LinearGradient(colors: [Color.green, Color.white], startPoint: .topLeading, endPoint: .bottomTrailing)
						}else {
							LinearGradient(colors: [Color.purple, Color.white], startPoint: .topLeading, endPoint: .bottomTrailing)
						}
						
						contentView()
					}
				}else {
					Color.gray.opacity(1)
				}
			}
			.mgPagingView(configure)
			
			HStack {
				Button {
					configure.scrollToPreviouse()
				} label: {
					Text("上一页")
				}
				
				Text("\(configure.currentIndex)")
				
				Text("\(configure.list.count)")
					.onTapGesture {
						randomList()
					}
				
				Button {
					configure.scrollToNext()
				} label: {
					Text("下 一页")
				}
				
			}
		
		}
		.mgTabMarkView({
			Color.yellow
				.offset(y: -20)
		})
		.onAppear {
//			configure.list = Array(repeating: <#T##RangeReplaceableCollection#>, count: <#T##Int#>)
//			configure.contentSpacing = 8
			randomList()
			configure.currentIndex = 0
			configure.showTabView = true
			configure.tabAlignment = .leading
		 }
	}
	
	func randomList() {
//		configure.list = []
//		DispatchQueue.main.async {
			configure.list = Array(repeating: 1, count: Int.random(in: 0...10))
		if configure.currentIndex > configure.list.count - 1 && configure.list.count > 0 {
			configure.currentIndex  = configure.list.count - 1
		}
//		}

	}
	
	@ViewBuilder
	func contentView() -> some View {
		ScrollView {
			
			VStack {
				Text("""
雨晴站在静园斑驳的铁门前，手中的邀请函已经被汗水浸湿了一角。这座隐匿在深山中的百年古宅比她想象中还要阴森——灰黑色的砖墙爬满了常春藤，尖顶的屋檐像猛禽展开的翅膀，投下不祥的阴影。

"有人吗？"她的声音在寂静的山林中显得格外突兀。

铁门"吱呀"一声自动打开，仿佛早就等待着她的到来。雨晴拖着行李箱走过鹅卵石铺就的小路，两旁的花草修剪得过分整齐，反而透着一股刻意维持的生硬感。

"周小姐？"一个沙哑的声音突然从侧面传来，吓得雨晴差点跳起来。

转头看见一位穿着黑色中山装的老人，脸上的皱纹深得像是用刀刻出来的。"我是老陈，这里的管家。老爷子在等您。"

雨晴跟着老陈穿过迷宫般的回廊，总觉得有什么东西在暗处窥视着自己。走廊两侧挂着历代主人的肖像，那些褪色的油画中，人物的眼睛似乎会随着她的移动而转动。

"您的房间在二楼西侧。"老陈推开一扇雕花木门，"书房在东翼，明天开始您的工作就是整理那里的古籍。"

雨晴点点头，目光却被走廊尽头一扇紧闭的房门吸引。那门上贴着一张泛黄的符纸，在无风的室内轻轻颤动。

"那是什么地方？"她忍不住问道。

老陈的表情瞬间凝固，"那不是您该关心的。记住，天黑后不要随意走动，特别是——"他顿了顿，"不要靠近书房里的那面镜子。"

当晚，雨晴躺在四柱床上辗转难眠。这座宅子太安静了，连虫鸣鸟叫都没有，只有老式座钟的滴答声在黑暗中格外清晰。

"咚、咚、咚"——午夜的钟声刚过，一阵细微的啜泣声突然从远处飘来。

雨晴猛地坐起身，那声音若有若无，像是从墙壁里渗出来的。她摸出手机打开手电筒功能，光柱在古老的壁纸上划出一道惨白。

声音似乎来自书房方向。

违背了老陈的警告，雨晴轻手轻脚地推开门。走廊比白天更加阴冷，她的拖鞋踩在地板上发出令人心惊的吱嘎声。转过最后一个拐角，她愣住了——书房门缝下透出一线幽蓝的光。

"有人吗？"她颤抖着推开门。

书房里空无一人，但那面被黑布遮盖的落地镜正在发光。雨晴的心脏狂跳，理智告诉她应该转身离开，但某种无法抗拒的冲动驱使她向前走去。

当她颤抖的手指碰到黑布边缘时，镜面突然剧烈震动起来。雨晴惊叫一声后退，黑布滑落在地——

镜中映出的不是她的倒影。

一个穿着淡紫色旗袍的年轻女子站在镜中，面容苍白如纸，漆黑的眼中流下两行血泪。她的嘴唇蠕动着，似乎在说着什么。

"救...我..."

雨晴的血液瞬间凝固。那女子抬起手按在镜面上，五指张开，掌心有一个奇怪的符号正在发光。更可怕的是，雨晴发现自己的右手掌心不知何时出现了同样的标记，正随着镜中符号的节奏一起脉动。

镜面突然如水波般荡漾起来，一股无形的力量拽着雨晴向前倾倒。她拼命挣扎，却眼睁睁看着自己的手臂穿过了镜面——

"周小姐！"

老陈的声音伴随着一盆冷水当头泼下。雨晴跌坐在地，惊恐地发现自己在书房中央，而那面镜子仍然被黑布遮盖着，仿佛从未被动过。

"我警告过您不要靠近这面镜子。"老陈的脸色比往常更加阴沉，手中的煤油灯在他脸上投下跳动的阴影。

"可是...我看到了..."雨晴语无伦次地指着镜子，"里面有个女人..."

老陈的眼神闪烁了一下，"您做了噩梦，仅此而已。现在，请回房休息。"他的语气不容置疑。

回到房间后，雨晴锁上门，瘫坐在床边。她颤抖着摊开右手——掌心那个奇怪的符号依然清晰可见，在月光下泛着淡淡的青光。

窗外，一轮血月悄然升起，为静园的屋顶镀上一层不祥的红光。书房方向传来细微的刮擦声，像是有人在用指甲轻轻挠着镜面...

---

**接下来故事可能会沿着这些方向发展**：

- **家族秘密**：老陈对古镜异常紧张的态度暗示他知晓更多关于这座宅子和镜子的秘密，可能与周家祖上的某些事件有关。

- **神秘符号**：雨晴掌心出现的符号是连接两个世界的钥匙，这个符号也出现在古宅的某些古籍和器物上，等待被解读。

- **时间循环**：镜中女子婉清似乎被困在1925年的某个特定日期，雨晴需要找出那天发生的事情才能打破循环，而这与静园曾经发生的一起命案有关。

希望这个故事能满足您的要求。如果需要更多细节或调整，请随时告诉我。						 
""")
			}
		}
	}
}

@available(iOS 14.0, *)
#Preview {
	MGPagingTestView()
}

open class MGPagingViewConfigure: NSObject, ObservableObject, UIScrollViewDelegate {
//	@Published var count: Int = 0
	@Published public var list: [Any] = []
	
	@Published private var contentSpacing: CGFloat = 8
	@Published public var showsContentIndicators: Bool = false
	@Published public var coordinateSpace: String = "MGPagingViewScroll"
	@Published public var tabScrollViewCoordinateSpace: String = "MGPagingTabViewScroll"
	@Published public var currentIndex: Int = 0
	@Published public var offsetX: CGFloat = 0.0
	@Published public var showTabView: Bool = true
	@Published public var tabAlignment: HorizontalAlignment = .leading
	@Published public var tabHeight: CGFloat = 44
	@Published public var tabItemSpacing: CGFloat = 8
	fileprivate var contentScrollView: UIScrollView?
	fileprivate var tabScrollView: UIScrollView?
	fileprivate var proxy: GeometryProxy?
	

	
	fileprivate var tabFrames: [Int: CGRect] = [:]
	
	func configure(_  scrollView: UIScrollView) {
		scrollView.isPagingEnabled = true
		if self.contentScrollView != scrollView  {
			self.contentScrollView = scrollView
			scrollView.delegate = self
		}
		
	}
	
	func configureTab(_ scrollView: UIScrollView) {
		if self.tabScrollView != scrollView {
			self.tabScrollView = scrollView
			tabScrollViewScrollToCenter(false)
		}
	}
	
	
	public func tabScrollViewScrollToCenter(_ animated: Bool = true) {
		
		guard currentIndex < tabFrames.count else {
			return
		}
		guard let frame = tabFrames[currentIndex]  else {
			return
		}
		guard let tabScrollView else {
			return
		}
		var offsetX = frame.midX
		
		
		if offsetX - tabScrollView.frame.size.width / 2.0 > 0 && offsetX + tabScrollView.frame.size.width / 2.0 < tabScrollView.contentSize.width {
			
			offsetX = offsetX - tabScrollView.frame.width / 2.0
		}else {
			
			if offsetX - tabScrollView.frame.size.width / 2.0 < 0 {
				offsetX = 0
			}
			if offsetX + tabScrollView.frame.size.width / 2.0 > tabScrollView.contentSize.width {
				
				offsetX = tabScrollView.contentSize.width - tabScrollView.frame.width
			}
		}
				
		tabScrollView.setContentOffset(CGPoint.init(x: offsetX, y: 0), animated: animated)
	}
	
	var scrollWillBeginDraging: Bool = false
	
	
	public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
		self.scrollWillBeginDraging = true
	}
	
	public func scrollViewDidScroll(_ scrollView: UIScrollView) {
		DispatchQueue.main.async {
			self.offsetX = -scrollView.contentOffset.x
		}
	}
	
	public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
		
		if let proxy {
//			if scrollWillBeginDraging {
				let x = -scrollView.contentOffset.x
				let index = Int(abs(x) / proxy.size.width)
				offsetX = x
				if index != currentIndex {
					currentIndex = index
				}
				
//				self.scrollWillBeginDraging = false
//			}
		}
	}
	
	
	
	public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
		if let proxy {
			let x = -scrollView.contentOffset.x
			let index = Int(abs(x) / proxy.size.width)
			offsetX = x
			if index != currentIndex {
				currentIndex = index
			}
		}
	}
	
	
	public func scrollToPreviouse(_ animated: Bool = true) {
		let count = list.count
		let index = min(count - 1, max(0, currentIndex - 1))
		scrollTo(index, animated: animated)
	}
	
	public func scrollToNext(_ animated: Bool = true) {
		let count = list.count
		let index = min(count - 1, max(0, currentIndex + 1))
		scrollTo(index, animated: animated)
	}
	
	
	public func scrollTo(_ index: Int, animated: Bool) {
		guard list.count > 0 else {
			return
		}
		
		let page = proxy?.size.width ?? 0
		contentScrollView?.setContentOffset(CGPoint.init(x: CGFloat(index) * page, y: 0), animated: animated)
		if !animated {
			DispatchQueue.main.async {
				self.currentIndex = index
			}
		}
	}
}
